/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/error.hpp>
#include <baja/assert.hpp>

namespace baja {

template <class Targs> class eventable;

template <class Targs> class event_source
{
public:
    typedef std::function<void(const Targs&, void*)> handler_type;
    friend class eventable<Targs>;
    event_source() : _nextCookie(0) 
    {
    }

    int32 operator+=(const handler_type& handler)
    {
        return this->addEventHandler(handler);
    }

    void operator-=(const int32 cookie)
    {
        return this->removeEventHandler(cookie);
    }

    int32 addEventHandler(const handler_type& handler, void* context = nullptr)
    {
        entry<Targs> e = entry<Targs>::create(++_nextCookie, handler, context);
        _handlers.push_back(e);
        return e.cookie;
    }

    void removeEventHandler(const int32 cookie)
    {
        auto it = std::find_if(
            _handlers.begin(), 
            _handlers.end(), 
            [&cookie](const entry<Targs>& e) { return e.cookie == cookie; }
        );

        if (it == _handlers.end())
        {
            BAJA_THROW(std::invalid_argument("couldn't find handler with given cookie"));
        }
        _handlers.erase(it);
    }

protected:
    template <class Targs>
    void raise(const Targs& args) const
    {
        std::for_each(
            _handlers.begin(),
            _handlers.end(),
            [&args](const entry<Targs>& e)
        {
            e.handler(args, e.context);
        });
    }

private:
    template <class Targs>
    struct entry
    {
        void* context;
        int32 cookie;
        handler_type handler;

        static entry create(const int32 cookie, const handler_type& handler, void* context)
        {
            return entry(cookie, handler, context);
        }
    private:
        entry(const int32 cookie_, const handler_type& handler_, void* context_ = nullptr) : cookie(cookie_), handler(handler_), context(context_) {}
    };

    int32 _nextCookie;
    std::vector<entry<Targs>> _handlers;
};

template <class Targs> class eventable
{
public:
    virtual ~eventable() {}

protected:
    void raise(const event_source<Targs>& source, const Targs& args)
    {
        source.raise(args);
    }
};

} // namespace baja